#include "Mapper004.h"
#include "../Famicom.h"
#include "../Devices.h"

void Mapper004::OnReset() {

    mRegid = 0;
    mPrmode = 0;
    mCrmode = 0;
    latch = 0;
    counter = 0;
    mIrqEnable = 0;


    //FormatBank(BANKSIZE_8KB, BANKSIZE_1KB);
    SetCPUBank_8KB(0, 0);
    SetCPUBank_8KB(1, 1);
    const int last = mROM->prg16kbCount * 2;
    SetCPUBank_8KB(2, last - 2);
    SetCPUBank_8KB(3, last - 1);

    // CHR-Rom
    for (int i = 0; i != 8; ++i)
        SetPPUBank_1KB(i, i);
}

void Mapper004::Hsync(uint32_t line) {
    if (!(famicom->GetDevices()->ppu()->mPPUMask
          & (((uint8_t)BGEN | (uint8_t)SPEN))))
        return;


    if (line > 239)
        return;

    if (mIrqReload) {
        counter = latch;
        mIrqReload = 0;
    }
    else if (counter > 0)
        counter--;

    if (counter == 0) {
        mIrqReload = 0xFF;
        if (mIrqEnable) {
            famicom->GetDevices()->cpu()->TryIrq(IRQ_HSYNC_INT);
        }
    }
#if 0
    if (counter) {
        --counter;
        if (!counter && mIrqEnable) {
            famicom->cpu->TryIrq(IRQ_HSYNC_INT);
        }
    }
    else {
        counter = latch;
    }
#endif
}

void Mapper004::SelectBank(uint8_t data)
{
    mRegid = data & 7;
    mPrmode = (data >> 6) & 1;
    mCrmode = (data >> 7) & 1;
    UpdatePPUBanks();
    UpdateCPUBanks();

}

void Mapper004::UpdateBankData(uint8_t bank)
{
    if (mRegid <= 5) {
        mRegs[mRegid] = bank;
        UpdatePPUBanks();
    }
    else {
        mRegs[mRegid] = bank & 0x3F;
        UpdateCPUBanks();
    }
}

void Mapper004::UpdateCPUBanks() {
    const int last = mROM->prg16kbCount * 2;
    if (mPrmode) {
        // (-2) R7 R6 (-1)
        SetCPUBank_8KB(0, last - 2);
        SetCPUBank_8KB(1, mRegs[7]);
        SetCPUBank_8KB(2, mRegs[6]);
        SetCPUBank_8KB(3, last - 1);
    }
    else {
        // R6 R7 (-2) (-1)
        SetCPUBank_8KB(0, mRegs[6]);
        SetCPUBank_8KB(1, mRegs[7]);
        SetCPUBank_8KB(2, last - 2);
        SetCPUBank_8KB(3, last - 1);
    }
}

void Mapper004::UpdatePPUBanks() {
    if (mCrmode) {
        // R2R3R4R5R0r0R1r1
        SetPPUBank_1KB(0, mRegs[2]);
        SetPPUBank_1KB(1, mRegs[3]);
        SetPPUBank_1KB(2, mRegs[4]);
        SetPPUBank_1KB(3, mRegs[5]);
        SetPPUBank_1KB(4, mRegs[0] & 0xFE);
        SetPPUBank_1KB(5, mRegs[0] | 1);
        SetPPUBank_1KB(6, mRegs[1] & 0xFE);
        SetPPUBank_1KB(7, mRegs[1] | 1);
    }
    else {
        // R0r0R1r1R2R3R4R5
        SetPPUBank_1KB(0, mRegs[0] & 0xFE);
        SetPPUBank_1KB(1, mRegs[0] | 1);
        SetPPUBank_1KB(2, mRegs[1] & 0xFE);
        SetPPUBank_1KB(3, mRegs[1] | 1);
        SetPPUBank_1KB(4, mRegs[2]);
        SetPPUBank_1KB(5, mRegs[3]);
        SetPPUBank_1KB(6, mRegs[4]);
        SetPPUBank_1KB(7, mRegs[5]);
    }


}

void Mapper004::UpdateMirroring(uint8_t data)
{
    if (!mROM->isFourScreen) {
        famicom->GetDevices()->ppu()->SwitchMirroring((data & 1)
                                        ? Mirror::HORIZONTAL
                                        : Mirror::VERTICAL);
    }

}

void Mapper004::WriteViaCPU(uint16_t address, uint8_t data) {
    switch (address & 0xE001)
    {
    case 0x8000:
        SelectBank(data);
        break;
    case 0x8001:
        UpdateBankData(data);
        break;
    case 0xA000:
        // $A000-$BFFE Mirroring
        UpdateMirroring(data);
        break;
    case 0xA001:
        // $A001-$BFFF PRG RAM protect
        if (data & 0x80)
        {
            // enable save RAM $6000-$7FFF
        }
        else
        {
            // disable save RAM $6000-$7FFF
        }
        break;
    case 0xC000:
        // $C000-$DFFE IRQ latch
        latch = data;
        break;
    case 0xC001:
        // $C001-$DFFF IRQ reload
        mIrqReload = 0xFF;
        counter = 0;
        break;
    case 0xE000:
        // $E000-$FFFE IRQ disable
        mIrqEnable = 0;
        famicom->GetDevices()->cpu()->ClrIrq(IRQ_HSYNC_INT);
        break;
    case 0xE001:
        // $E001-$FFFF IRQ enable
        mIrqEnable = 1;
        break;
    }
}

void Mapper004::OnSave(SaveBundle *bundle)
{
    bundle->SaveByte(mRegid);
    bundle->SaveByte(mPrmode);
    bundle->SaveByte(mCrmode);
    bundle->SaveBlock(mRegs, 8);
    bundle->SaveByte(latch);
    bundle->SaveByte(counter);
    bundle->SaveByte(mIrqEnable);
    bundle->SaveByte(mIrqReload);
}

void Mapper004::OnRestore(SaveBundle *bundle)
{
    mRegid = bundle->RestoreByte();
    mPrmode = bundle->RestoreByte();
    mCrmode = bundle->RestoreByte();
    bundle->RestoreBlock(mRegs, 8);
    latch = bundle->RestoreByte();
    counter = bundle->RestoreByte();
    mIrqEnable = bundle->RestoreByte();
    mIrqReload = bundle->RestoreByte();
}
